package top.go2do.tagCategory.business.tag.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import jakarta.persistence.criteria.Predicate;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import top.go2do.common.dao.ApplicationRepository;
import top.go2do.common.dao.TagRepository;
import top.go2do.common.dao.UserRepository;
import top.go2do.common.enumeration.OperationStatus;
import top.go2do.common.enumeration.OperationType;
import top.go2do.common.filter.RequestIdFilter;
import top.go2do.common.module.ApplicationPO;
import top.go2do.common.module.OperationLogBO;
import top.go2do.common.module.TagPO;
import top.go2do.common.module.UserPO;
import top.go2do.common.module.pageQuery.PageResult;
import top.go2do.common.operationLog.IRecorder;
import top.go2do.common.utils.IdUtils;
import top.go2do.common.utils.VersionUtils;
import top.go2do.tagCategory.business.tag.enumeration.TagStatus;
import top.go2do.tagCategory.business.tag.module.*;
import top.go2do.tagCategory.exception.TagCategoryErrorCode;
import top.go2do.tagCategory.exception.TagCategoryException;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author xingyj
 * @CreateTime 2025/3/28 17:34
 */
@Service
@Slf4j
public class TagServiceImpl implements TagService {

    @Autowired
    private TagRepository tagRepository;

    @Autowired
    private IdUtils idUtils;

    @Autowired
    private IRecorder recorder;

    @Autowired
    private ApplicationRepository applicationRepository;

    @Autowired
    private UserRepository userRepository;

    @Transactional(rollbackOn = Exception.class)
    public void create(TagPO tagPO) {

        /**
         * step: 生成实体相关字段值，插入数据库
         */
        Long tagId = idUtils.getSnowflakeId();
        tagPO.setTagId(tagId);
        tagPO.setStatus(TagStatus.NORMAL.getValue());
        Long operatorId = tagPO.getCreatedBy();
        String operator = String.valueOf(operatorId);
        String version = VersionUtils.generateVersion(tagPO, operator);
        tagPO.setVersion(version);
        tagRepository.save(tagPO);

        /**
         * step: 插入数据库后，记录操作日志
         */
        String beforeVersion = "";
        String beforeData = "";
        String afterData = JSONUtil.toJsonStr(tagPO);
        recordTagLog(tagPO.getAppId(), operatorId, tagId, beforeVersion, version, beforeData, afterData);
    }


    @Override
    public TagPO create(TagCreateForm createForm) {
        String appId = createForm.getAppId();
        String entityCode = createForm.getEntityCode();
        String tagName = createForm.getTagName();
        TagPO tagPO = tagRepository.findByAppIdAndEntityCodeAndTagNameAndCreatedBy(appId, entityCode, tagName, createForm.getUserId());
        if (tagPO != null) {
            throw new TagCategoryException(TagCategoryErrorCode.CATEGORY_NAME_EXIST);
        }
        tagPO = new TagPO();
        tagPO.setAppId(appId);
        BeanUtils.copyProperties(createForm, tagPO);
        Long tagId = idUtils.getSnowflakeId();
        Long userId = createForm.getUserId();
        Integer sort = createForm.getSort();
        
        tagPO.setTagId(tagId);
        tagPO.setCreatedBy(userId);
        tagPO.setUpdatedBy(userId);
        String operator = String.valueOf(createForm.getUserId());
        String version = VersionUtils.generateVersion(tagPO, operator);
        tagPO.setVersion(version);
        if (sort != null && sort > 0) {
            tagPO.setSort(sort);
        }
        tagRepository.save(tagPO);
        return this.getById(tagId);
    }

    @Override
    public void modifyById(TagModifyForm modifyForm) {
        Long tagId = modifyForm.getTagId();
        TagPO tagPO = this.getById(tagId);
        String tagName = modifyForm.getTagName();
        String entityCode = modifyForm.getEntityCode();
        Integer sort = modifyForm.getSort();
        String appId = tagPO.getAppId();
        if (StringUtils.hasText(tagName)) {
            TagPO target = tagRepository.findByAppIdAndEntityCodeAndTagNameAndCreatedBy(appId, entityCode, tagName, modifyForm.getUserId());
            if (target != null && !target.getTagId().equals(tagId)) {
                throw new TagCategoryException(TagCategoryErrorCode.CATEGORY_NAME_EXIST);
            }
            tagPO.setTagName(tagName);
        }
        tagPO.setUpdatedBy(modifyForm.getUserId());
        if (sort != null) {
            tagPO.setSort(sort);
        }

        String operator = String.valueOf(modifyForm.getUserId());
        String version = VersionUtils.generateVersion(tagPO, operator);
        tagPO.setVersion(version);
        tagRepository.save(tagPO);
    }

    @Override
    public void removeById(Long id) {
        this.getById(id);
        tagRepository.deleteById(id);
    }

    @Override
    public TagPO getById(Long id) {
        TagPO tagPO = tagRepository.findById(id).orElse(null);
        if (tagPO == null) {
            throw new TagCategoryException(TagCategoryErrorCode.CATEGORY_ID_NOT_EXIST);
        }
        return tagPO;
    }

    @Override
    public PageResult<TagPO> pageQuery(TagQueryCondition condition) {
        Sort.Direction direction = Sort.Direction.fromString(condition.getDirection());
        Sort sort = Sort.by(direction, condition.getOrderBy());

        /**
         * pageNumber 从0开始
         */
        Integer page = condition.getPage() - 1;
        Integer size = condition.getSize();
        Pageable pageable = PageRequest.of(page, size, sort);
        Specification<TagPO> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (condition.getAppId() != null) {
                predicates.add(cb.equal(root.get("appId"), condition.getAppId()));
            }

            if (condition.getUserId() != null) {
                predicates.add(cb.equal(root.get("createdBy"), condition.getUserId()));
            }

            if (condition.getEntityCode() != null) {
                predicates.add(cb.equal(root.get("entityCode"), condition.getEntityCode()));
            }

            if (condition.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), condition.getStatus()));
            }

            if (condition.getTagName() != null) {
                predicates.add(cb.like(root.get("tagName"), "%" + condition.getTagName() + "%"));
            }

            if (condition.getKeyword() != null) {
                Predicate appIdMatch = cb.like(root.get("tagName"), "%" + condition.getKeyword() + "%");
                Predicate appNameMatch = cb.like(root.get("description"), "%" + condition.getKeyword() + "%");
                predicates.add(cb.or(appIdMatch, appNameMatch)); // 核心修改点
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        };
        Page<TagPO> queryResult = tagRepository.findAll(spec, pageable);
        PageResult<TagPO> pageResult = new PageResult(queryResult.getNumber(), queryResult.getSize(), queryResult.getTotalElements(), queryResult.getTotalPages());
        pageResult.setList(queryResult.getContent());
        return pageResult;
    }

    @Override
    public List<TagPO> listAll(TagQueryAllCondition condition) {
        Sort.Direction direction = Sort.Direction.fromString(condition.getDirection());
        Sort sort = Sort.by(direction, condition.getOrderBy());

        Specification<TagPO> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (condition.getAppId() != null) {
                predicates.add(cb.equal(root.get("appId"), condition.getAppId()));
            }
            if (condition.getUserId() != null) {
                predicates.add(cb.equal(root.get("createdBy"), condition.getUserId()));
            }

            if (condition.getEntityCode() != null) {
                predicates.add(cb.equal(root.get("entityCode"), condition.getEntityCode()));
            }

            if (condition.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), condition.getStatus()));
            }

            if (condition.getTagName() != null) {
                predicates.add(cb.like(root.get("tagName"), "%" + condition.getTagName() + "%"));
            }

            if (condition.getKeyword() != null) {
                Predicate appIdMatch = cb.like(root.get("tagName"), "%" + condition.getKeyword() + "%");
                Predicate appNameMatch = cb.like(root.get("description"), "%" + condition.getKeyword() + "%");
                predicates.add(cb.or(appIdMatch, appNameMatch)); // 核心修改点
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        };
        return tagRepository.findAll(spec, sort);
    }

    @Override
    public List<TagListVO> po2vo(List<TagPO> list) {
        Set<String> appIdSet = new HashSet<>();
        Set<Long> userIdSet = new HashSet<>();
        for (TagPO po : list) {
            appIdSet.add(po.getAppId());
            userIdSet.add(po.getCreatedBy());
            userIdSet.add(po.getUpdatedBy());
        }
        List<ApplicationPO> appList = applicationRepository.findAllById(appIdSet);
        List<UserPO> userList = userRepository.findAllById(userIdSet);
        Map<String, String> appId2NameMap;
        Map<Long, String> userId2NameMap;
        if (appList != null) {
            appId2NameMap = appList.stream().collect(Collectors.toMap(ApplicationPO::getAppId, ApplicationPO::getAppName));
        } else {
            appId2NameMap = new HashMap<>(0);
        }

        if (userList != null) {
            userId2NameMap = userList.stream().collect(Collectors.toMap(UserPO::getUserId, UserPO::getUsername));
        } else {
            userId2NameMap = new HashMap<>(0);
        }
        List<TagListVO> voList = list.stream().map(po -> {
            TagListVO vo = new TagListVO();
            BeanUtils.copyProperties(po, vo);
            vo.setAppName(appId2NameMap.get(po.getAppId()));
            vo.setCreatedByName(userId2NameMap.get(po.getCreatedBy()));
            vo.setUpdatedByName(userId2NameMap.get(po.getUpdatedBy()));
            vo.setTagId(String.valueOf(po.getTagId()));
            vo.setUpdatedBy(String.valueOf(po.getUpdatedBy()));
            vo.setCreatedBy(String.valueOf(po.getCreatedBy()));
            return vo;
        }).toList();
        return voList;
    }

    private void recordTagLog(String appId, Long operatorId, Long tagId, String beforeVersion, String afterVersion, String beforeData, String afterData) {
        OperationLogBO operationLogBO = new OperationLogBO();
        operationLogBO.setAppId(appId);
        operationLogBO.setOperatorId(operatorId);

        // 输出示例: 2025-05-21 15:30:45
        String operationTime = DateUtil.now();
        operationLogBO.setOperationTime(operationTime);
        OperationType operationType = OperationType.CREATE_TAG;
        operationLogBO.setOperationType(operationType.getValue());
        operationLogBO.setTargetType(operationType.getTargetType());
        operationLogBO.setTargetId(String.valueOf(tagId));
        operationLogBO.setOperationStatus(OperationStatus.SUCCESS.getValue());
        String requestId = MDC.get(RequestIdFilter.REQUEST_ID_MDC);
        String deviceId = MDC.get(RequestIdFilter.DEVICE_ID_MDC);
        operationLogBO.setRequestId(requestId);
        operationLogBO.setDeviceId(deviceId);
        operationLogBO.setBeforeVersion(beforeVersion);
        operationLogBO.setAfterData(afterVersion);
        operationLogBO.setBeforeData(beforeData);
        operationLogBO.setAfterData(afterData);
        recorder.sendMessage(operationLogBO);
    }

}
